/*
  1. 我们定义一个函数，当这个函数的参数和返回值可以确定为字符串类型的时候
     那么我们可以指定参数的类型，返回的类型
  2. 问题来了，如果我们不知道参数的类型是什么，这时候应该怎么用ts定义?
      - 将返回值和参数类型都定义为any, 但是这里不推荐，因为这与js没想差别，违背了ts的强制类型定义
      - 这个时候，泛型就解决了这一问题
  3. 泛型应用于函数，接口，类中
  4. 泛型的特点是：不预先指定类型, 在使用的时候指定类型
*/
function echo(param: string): string {
  return param;
}

function randomEcho<T>(param: T): T {
  return param;
}
const result = randomEcho([123, 'string']); // 参数随便给
console.log(result);

// 场景: 给定一个元组，里面有两个元素，将这两个元素调换位置
// 注意: 由于是调换位置，T和U在返回中要调换位置
/*
  1. <T, U> 尖括号中出现的泛型个数，代表函数中类型出现的个数
  2. 因为要返回数组中两个元素的位置对调，并且两个元素的类型不同
  3. 所以需要多个泛型来解决定义的问题
  - 这一问题总的来说：一部分是让代码的定义更加的清晰易懂，一部分也对应着逻辑
*/
function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}
const result1 = swap(['string', 123]);
console.log(result1);

// 场景: 一个函数有两个参数，参数一传值为字符串类型的时候，参数二也必须是同类型
// 这里不能返回T类型是因为T可能代表着任意类型，然而return 返回的是字符串, 所以书写返回T类型会提示错误
function same<T>(first: T, second: T) {
  return `${first}${second}`;
}

// 函数执行时指定类型
const c = same<string | number>('1', '2');
const d = same<number>(1, 2);

// 函数泛型的多种姿势
function some<T>(param: T[]): T[] {
  return param;
}
some<string>(['1', '2']);

function sable<T>(param: Array<T>) {
  return param;
}
sable<string>(['1', '2']);

// 场景: 一个函数有两个参数，参数一传值为某类型的时候，参数二是其他类型
// 利用泛型进行灵活配置
function exchange<T, U>(first: T, second: U) {
  return `${first}${second}`;
}
exchange<string, number>('1', 2);
exchange('1', 2); // 类型推断 函数名后不写<string, number>也可以